﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
using OUTSys.DadLink.Utilities;
using OUTSys.Imagine.Behaviors;


namespace OUTSys.Imagine.Controls
{
    /// <summary>
    /// Creates graphic interfaces for entities that uses <seealso cref="System.ComponentModel"/> attributes
    /// </summary>
    public class Scaffolder : ContentControl
    {
        /// <summary>
        /// The type of the panel used to host the rendered controls
        /// </summary>
        public ScaffolderPanelType PanelType { get; set; }

        /// <summary>
        /// The orientation of the controls inside their panel
        /// </summary>
        public Orientation Orientation { get; set; }

        /// <summary>
        /// When true, collapse the container panel
        /// </summary>
        public bool CollapseOnEmpty { get; set; }

        /// <summary>
        /// The style of all the <seealso cref="TextBlock"/>
        /// </summary>
        public Style TextBlockStyle
        {
            get { return (Style)GetValue(TextBlockStyleProperty); }
            set { SetValue(TextBlockStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TextBlockStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextBlockStyleProperty =
            DependencyProperty.Register("TextBlockStyle", typeof(Style), typeof(Scaffolder), new UIPropertyMetadata(null));

        /// <summary>
        /// The style of all the <seealso cref="TextBox"/>
        /// </summary>
        public Style TextBoxStyle
        {
            get { return (Style)GetValue(TextBoxStyleProperty); }
            set { SetValue(TextBoxStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for TextBoxStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TextBoxStyleProperty =
            DependencyProperty.Register("TextBoxStyle", typeof(Style), typeof(Scaffolder), new UIPropertyMetadata(null));

        /// <summary>
        /// The style of all the <seealso cref="ComboBox"/>
        /// </summary>
        public Style ComboBoxStyle
        {
            get { return (Style)GetValue(ComboBoxStyleProperty); }
            set { SetValue(ComboBoxStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ComboBoxStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ComboBoxStyleProperty =
            DependencyProperty.Register("ComboBoxStyle", typeof(Style), typeof(Scaffolder), new UIPropertyMetadata(null));

        /// <summary>
        /// The style of all the <seealso cref="DatePicker"/>
        /// </summary>
        public Style DatePickerStyle
        {
            get { return (Style)GetValue(DatePickerStyleProperty); }
            set { SetValue(DatePickerStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DatePickerStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DatePickerStyleProperty =
            DependencyProperty.Register("DatePickerStyle", typeof(Style), typeof(Scaffolder), new UIPropertyMetadata(null));

        /// <summary>
        /// The style of all the <seealso cref="CheckBox"/>
        /// </summary>
        public Style CheckBoxStyle
        {
            get { return (Style)GetValue(CheckBoxStyleProperty); }
            set { SetValue(CheckBoxStyleProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CheckBoxStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CheckBoxStyleProperty =
            DependencyProperty.Register("CheckBoxStyle", typeof(Style), typeof(Scaffolder), new UIPropertyMetadata(null));

        public Scaffolder()
        {
            this.DataContextChanged += new DependencyPropertyChangedEventHandler(Scaffolder_DataContextChanged);
        }

        void Scaffolder_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
            {
                this.SetValue(ContentControl.ContentProperty, BuildParent());
                this.Visibility = System.Windows.Visibility.Collapsed;
            }
            else
            {
                var contents = BuildContents();
                if (VisualTreeHelper.GetChildrenCount(contents) == 0)
                    this.Visibility = System.Windows.Visibility.Collapsed;
                else
                    this.Visibility = System.Windows.Visibility.Visible;
                this.SetValue(ContentControl.ContentProperty, contents);
            }
        }

        /// <summary>
        /// Build the contents of the control
        /// </summary>
        /// <returns></returns>
        protected virtual FrameworkElement BuildContents()
        {
            //Gets all the properties that apply the BindableAttribute 
            var members = Reflector.GetPropertiesWithAttribute(this.DataContext, typeof(BindableAttribute), typeof(ListBindableAttribute));

            if (members == null || members.Count == 0) return new FrameworkElement();

            //Creates the panel based in the user choice
            Panel parent = BuildParent();

            //Iterates through all the properties
            foreach (var m in members)
            {
                //Checks if the property also uses the DisplayName attribute, if so, then creates a "label" for it
                var des = (DisplayNameAttribute)m.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
                if (des != null)
                {
                    //Creates a textbox before the control
                    TextBlock tbDes = new TextBlock();
                    tbDes.Text = des.DisplayName;
                    if (TextBlockStyle != null)
                        tbDes.Style = TextBlockStyle;
                    parent.Children.Add(tbDes);
                }

                //Verifies if the properti uses the BindableAttribute
                if (Attribute.IsDefined(m, typeof(BindableAttribute)))
                {
                    FrameworkElement element = null;
                    DependencyProperty property = null;

                    //If the type of the property is System.String or System.Int, creates a textbox
                    if (m.PropertyType == typeof(int) || m.PropertyType == typeof(string))
                    {
                        element = new TextBox();
                        if (TextBoxStyle != null)
                            element.Style = TextBoxStyle;
                        property = TextBox.TextProperty;
                    }

                    //If the type of the property is System.DateTime, creates a DatePicker
                    if (m.PropertyType == typeof(DateTime))
                    {
                        element = new DatePicker();
                        if (DatePickerStyle != null)
                            element.Style = DatePickerStyle;
                        property = DatePicker.SelectedDateProperty;
                    }

                    //If the type of the property is System.Boolean, it creates a CheckBox
                    if (m.PropertyType == typeof(bool) || m.PropertyType == typeof(Nullable<bool>))
                    {
                        element = new CheckBox();
                        if (CheckBoxStyle != null)
                            element.Style = CheckBoxStyle;
                        property = CheckBox.IsCheckedProperty;
                    }
                    BindAndAdd(parent, element, m.Name, property);
                }

                //Validates the property uses the ListBindableAttribute
                if (Attribute.IsDefined(m, typeof(ListBindableAttribute)))
                {
                    var selectedItem = Reflector.GetAttributes<ComplexBindingPropertiesAttribute>(this.DataContext)
                        .Where(a => a.DataSource == m.Name)
                        .FirstOrDefault();

                    //Creates a ComboBox for this kind of properties
                    if (selectedItem != null)
                    {
                        var combo = new ComboBox();
                        if (ComboBoxStyle != null)
                            combo.Style = ComboBoxStyle;

                        combo.SetValue(ComboBoxWidthFromItemsBehavior.ComboBoxWidthFromItemsProperty, true);

                        if (m.PropertyType.IsGenericType && m.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
                        {
                            combo.SelectedValuePath = "Key";
                            combo.DisplayMemberPath = "Value";
                        }

                        BindListAndAdd(parent, combo, m.Name, ComboBox.ItemsSourceProperty, selectedItem.DataMember, ComboBox.SelectedItemProperty);

                    }
                    else
                    {
                        var listBox = new ListBox();
                        if (m.PropertyType.IsGenericType && m.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
                        {
                            listBox.SelectedValuePath = "Key";
                            listBox.DisplayMemberPath = "Value";
                        }

                        BindAndAdd(parent, listBox, m.Name, ListBox.ItemsSourceProperty);
                    }
                }
            }

            return parent;

        }

        /// <summary>
        /// Creates the content panel that host all the controls
        /// </summary>
        /// <returns></returns>
        private Panel BuildParent()
        {
            Panel panel;

            switch (PanelType)
            {
                case ScaffolderPanelType.StackPanel:
                    panel = new StackPanel();
                    panel.SetValue(StackPanel.OrientationProperty, this.Orientation);
                    break;
                case ScaffolderPanelType.Grid:
                    panel = new Grid();
                    break;
                case ScaffolderPanelType.UniformGrid:
                    panel = new UniformGrid();
                    break;
                case ScaffolderPanelType.WrapPanel:
                    panel = new WrapPanel();
                    panel.SetValue(WrapPanel.OrientationProperty, this.Orientation);
                    break;
                default:
                    panel = null;
                    break;
            }
            panel.SetBinding(Panel.HorizontalAlignmentProperty, new Binding("HorizontalAlignment") { Source = this });
            panel.SetBinding(Panel.VerticalAlignmentProperty, new Binding("VerticalAlignment") { Source = this });
            panel.SetBinding(Panel.MarginProperty, new Binding("Margin") { Source = this });


            return panel;
        }

        /// <summary>
        /// Binds the controls and add them to the parent panel
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="element"></param>
        /// <param name="bindName"></param>
        /// <param name="property"></param>
        private void BindAndAdd(Panel parent, FrameworkElement element, string bindName, DependencyProperty property)
        {
            Binding bind = new Binding(bindName);
            bind.Source = this.DataContext;
            bind.Mode = BindingMode.TwoWay;
            element.SetBinding(property, bind);

            parent.Children.Add(element);
        }

        /// <summary>
        /// Bind list items to controls and add them to a list
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="element"></param>
        /// <param name="bindName"></param>
        /// <param name="sourceProperty"></param>
        /// <param name="selectedName"></param>
        /// <param name="selectedProperty"></param>
        private void BindListAndAdd(Panel parent, FrameworkElement element, string bindName, DependencyProperty sourceProperty, string selectedName, DependencyProperty selectedProperty)
        {
            Binding bindSource = new Binding(bindName);
            bindSource.Source = this.DataContext;
            bindSource.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
            element.SetBinding(sourceProperty, bindSource);

            Binding bindSelected = new Binding(selectedName);
            bindSelected.Source = this.DataContext;
            bindSelected.Mode = BindingMode.TwoWay;
            element.SetBinding(selectedProperty, bindSelected);
            parent.Children.Add(element);
        }
    }

    /// <summary>
    /// Represents the types of panel used by the <seealso cref="Scaffolder"/> control to host the child items
    /// </summary>
    public enum ScaffolderPanelType
    {
        StackPanel,
        Grid,
        UniformGrid,
        WrapPanel
    }
}
